package de.tudresden.inf.ggp.basicplayer;

import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;

import org.eclipse.palamedes.gdl.core.model.IGameNode;
import org.eclipse.palamedes.gdl.core.model.IGameState;
import org.eclipse.palamedes.gdl.core.model.IMove;
import org.eclipse.palamedes.gdl.core.simulation.Match;
import org.eclipse.palamedes.gdl.core.simulation.strategies.AbstractStrategy;

public class TriStra extends AbstractStrategy{
	public Map<IGameState, Integer> VisitedStates=new HashMap<IGameState,Integer>();				//store all of state, which we have computed
	final int MaxListNodes = 10000;
	final int MaxGoalValue = 100;
	int LearnedDepthLimit;
	int CtrVisitedStates;
	int CtrHasVisited;
	int CtrTerminal;
	int CtrNotTerminal;
	int CtrFoundUnique;
	int CtrNotFoundUnique;
	int CtrNotFinishedState;
	int Ctr0;
	int Steps;
	MyStru MaxStru1; 
	int RoleCount;
	String[] RoleNames;
	String MyRoleName;

	public List<IMove[]> getCombinedMovedfromState(IGameState TheState) throws InterruptedException{
		// resolveMoves
	    IMove[][] moves = new IMove[RoleCount][];
	    for (int i = 0; i < RoleCount; i++) 
	    	moves[i] = match.getGame().getReasoner().getLegalMoves(RoleNames[i], TheState);
	    // JoinedMoves
	    TheState.setLegalMoves(moves);
		return TheState.getCombinedLegalMoves();
	}
		
	public TriStra(){
    	System.out.println("[ATAX] -> MyGoingThrough Strategy 3.0 created now...");
    	VisitedStates.clear();
    	System.out.println("[ATAX] -> My ValuedNodes, and VisitedNodes are cleared in Strategy's constructor!");
    }
	
	@Override
	public void initMatch(Match initMatch){
		super.initMatch(initMatch);
    	VisitedStates.clear();
    	LearnedDepthLimit=1;
    	CtrVisitedStates=0;
    	CtrTerminal=0;
    	CtrNotTerminal=0;
    	Steps=0;
    	RoleNames = match.getGame().getRoleNames();
    	RoleCount = RoleNames.length;
    	MyRoleName = match.getRole();
    	System.out.println("[ATAX] -> My Role = "+MyRoleName);
		System.out.println("[ATAX] -> Hello, start time: "+ match.getStartTime());
		
		IGameState TheState = match.getCurrentNode().getState();

		try{
			List<IMove[]> moves = getCombinedMovedfromState(TheState);
	    	if (moves==null){
	    		System.out.println("combined moves in initial is null");
	    	}else{
	    		System.out.println("combined moves in initial is okay");
	    	}
		}catch(InterruptedException e){
    		e.printStackTrace();
    	}
	    // started thread to search...
		System.out.println("[ATAX] -> Starting to search");
    	int startclock = (match.getStartTime()-2)*1000;
    	MyThread SolFinder = new MyThread(match.getCurrentNode().getState(),true);
    	SolFinder.start();
		try {
			SolFinder.join(startclock); // wait at most clock seconds for solFinder to finish
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		while(SolFinder.isAlive()) { // if solFinder is still running interrupt it and wait for it to finish
	    	SolFinder.interrupt();
	    	try {
	    		SolFinder.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	class MyThread extends Thread{
		public boolean IsStartTime=false;
		public int CurrentDepthLimit;
		public IGameState CurrentState;
		public int MoveNumber;
		public int MaxValue;
		public MyThread(IGameState CurrentStateValue, boolean IsStartTimeValue){
			this.CurrentState = CurrentStateValue;
			this.IsStartTime = IsStartTimeValue;
//			if (this.IsStartTime == false){
//				CurrentDepthLimit = LearnedDepthLimit;
//			}else{
				CurrentDepthLimit = 1;
//			}
		}
		
		public void run(){
			MaxValue=0;
			MoveNumber=-1;
			if (CurrentState!=null){
				if (CurrentState.isTerminal()==false) {
					try {				
						boolean finished=false;
						while ((this.isInterrupted()==false)&&(finished==false)){
							VisitedStates.clear();
							CtrTerminal=0;
							CtrNotTerminal=0;
							CtrFoundUnique=0;
							CtrNotFoundUnique=0;
							CtrNotFinishedState=0;
							CtrHasVisited=0;
							Ctr0=0;
							int LocalMaxValue=0;
							int LocalMoveNumber=0;
							int i=0;
							List<IMove[]> moves = getCombinedMovedfromState(CurrentState); 
							if (moves!=null){
								IGameState TheNextState = null;
								LocalMaxValue=0;
								LocalMoveNumber=-1;
								int TempValue=0;
								i=0;
								System.out.println("[ATAX] -> Our Move size="+moves.size()+"; CurrentDepthLimit="+CurrentDepthLimit);
								while ((this.isInterrupted()==false)&&(i<moves.size())){
									TheNextState=match.getGame().getReasoner().getNextState(CurrentState, moves.get(i));
									VisitedStates.put(TheNextState, CtrVisitedStates);
									TempValue=expandVisitedState(TheNextState,CurrentDepthLimit,0);
	//								System.out.println("[ATAX] -> TempValue in Run="+TempValue+"; LocalMaxValue="+LocalMaxValue+"; LocalMoveNumber="+LocalMoveNumber);
									if (TempValue>LocalMaxValue){
										LocalMaxValue=TempValue;
										LocalMoveNumber=i;
									}
//									System.out.println("[ATAX] -> LocalMaxValue in Run after compare="+LocalMaxValue+"; LocalMoveNumber="+LocalMoveNumber);
									i++;
								}
								if ((i>=moves.size())||(MoveNumber<0)) {
									MaxValue=LocalMaxValue;
									MoveNumber=LocalMoveNumber;
								}
	//							System.out.println("[ATAX] -> i="+i+"; Moves.size()"+moves.size()+"; MaxValue in Run="+MaxValue+"; MoveNumber="+MoveNumber);
							}
							if (this.IsStartTime==false) LearnedDepthLimit = CurrentDepthLimit;
							CurrentDepthLimit++;
							if (CtrNotFinishedState==0){
								finished=true;
							}
							System.out.println("[ATAX] -> CurrentDepth="+(CurrentDepthLimit-1)+"; CtrTerminal="+CtrTerminal+"; CtrNotTerminal="+CtrNotTerminal+"; CtrNotFinishedState="+CtrNotFinishedState+"; Finished="+finished+"; CtrFoundUnique="+CtrFoundUnique+"; CtrNotFoundUnique="+CtrNotFoundUnique);
							System.out.println("[ATAX] -> CtrTerminal="+CtrTerminal+"; Ctr0="+Ctr0+"; CtrHasVisited="+CtrHasVisited);
							System.out.println("[ATAX] -> in Thread, Our Best Move, number="+MoveNumber+"; Value="+MaxValue);
						}
					} catch (InterruptedException e) {
						System.out.println("[ATAX] -> My Thread.run intterupted during expandVisitedState()");
					}
				} 
			}
		}
		
		public IGameState getUniqueNextState(IGameState TheState, List<IMove[]> moves)throws InterruptedException{
			List<Integer> ChoiceList = new ArrayList<Integer>();
			for (int i=0; i<moves.size();i++){
				ChoiceList.add(i);
			}
			int Choice=random.nextInt( ChoiceList.size() );
			IGameState TheNextState=match.getGame().getReasoner().getNextState(TheState, moves.get(ChoiceList.get(Choice)));
			while ((ChoiceList.size()>0) && (VisitedStates.containsKey(TheNextState))){
				ChoiceList.remove(Choice);
				if (ChoiceList.size()>0){
					Choice = random.nextInt( ChoiceList.size() );
					TheNextState=match.getGame().getReasoner().getNextState(TheState, moves.get(ChoiceList.get(Choice)));
				}
			}	
			if (ChoiceList.size()<=0){
				CtrNotFoundUnique++;
				return match.getGame().getReasoner().getNextState(TheState, moves.get(random.nextInt( moves.size() )));
			} else {
				CtrFoundUnique++;
				return TheNextState;	
			}
				
		}
		
		public int goGetTerminal(IGameState TheState, int CurrentDepth) throws InterruptedException{
//			if(Thread.interrupted()) {
//				throw new InterruptedException();
//			}
			if (TheState!=null){
				if (TheState.isTerminal()==true){
					CtrTerminal++;
					int TheValue=match.getGame().getReasoner().getGoalValue(MyRoleName,TheState);
					if (TheValue==0) Ctr0++;
					return TheValue;
				} else 
				if (this.isInterrupted()==false) {
					List<IMove[]> moves = getCombinedMovedfromState(TheState); 
					if ((moves!=null)&& (this.isInterrupted()==false)){
						IGameState TheNextState = null;
						TheNextState = getUniqueNextState(TheState, moves);
						if (VisitedStates.containsKey(TheNextState)){
							CtrHasVisited++;
							CtrNotTerminal++;
							return match.getGame().getReasoner().getGoalValue(MyRoleName,TheState);
						}else{
							CtrVisitedStates++;
							VisitedStates.put(TheNextState, CtrVisitedStates);
							return goGetTerminal(TheNextState, CurrentDepth+1);
						}
					}
				}
			}
			return 0;
		}
		
		public int expandVisitedState(IGameState TheState, int DepthLimit, int CurrentDepth) throws InterruptedException{
//			if(Thread.interrupted()) {
//				throw new InterruptedException();
//			}
			if (TheState.isTerminal()==true){
				CtrTerminal++;
				int TheValue = match.getGame().getReasoner().getGoalValue(MyRoleName,TheState);
				if (TheValue==0) Ctr0++;
				return TheValue;
			} else if (CurrentDepth>DepthLimit) {
				CtrNotFinishedState++;
				return goGetTerminal(TheState, CurrentDepth+1);
			} else {
				//MyStru TempStru = null;
				List<IMove[]> moves = getCombinedMovedfromState(TheState); 
				if (moves!=null){
					IGameState TheNextState = null;
					int TotalValue=0;
					int CtrValue=0;
					int i=0;
					while ((this.isInterrupted()==false)&&(i<moves.size())){
						TheNextState=match.getGame().getReasoner().getNextState(TheState, moves.get(i));
						if (VisitedStates.containsKey(TheNextState)){
							CtrHasVisited++;
						}else{
							CtrVisitedStates++;
							VisitedStates.put(TheNextState, CtrVisitedStates);
							TotalValue=TotalValue+expandVisitedState(TheNextState,DepthLimit,CurrentDepth+1);
		//					System.out.println("Temp Value="+TotalValue+"; in Depth"+CurrentDepth);
							CtrValue++;
						}
						i++;
					}
					if (CtrValue==0){
						return 0;
					}else {
						int AvgValue=(int) TotalValue/CtrValue;
//						System.out.println("Total Value="+TotalValue+"; Counter="+CtrValue+"; Avg Value="+AvgValue+"; in Depth"+CurrentDepth);
						return  AvgValue;
					}
				}
				return 0;
			}	
		}
		
	}

	public IMove getMove(IGameNode currentNode) {
		Steps++;
		List<IMove[]> moves = null;
		IMove[] mymove = null;
		if (moves==null){
			try{
				moves = getCombinedMovedfromState(match.getCurrentNode().getState()); 
				mymove=match.getGame().getReasoner().getLegalMoves(MyRoleName, currentNode.getState());
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		int MoveNumber=0;
		if (mymove.length>1){
			VisitedStates.clear();
			System.out.println("[ATAX] -> Starting to search in getMove() for step="+Steps);
	    	int playclock = (match.getPlayTime()-1)*1000;
	    	MyThread SolFinder = new MyThread(match.getCurrentNode().getState(),false);
	    	SolFinder.start();
			try {
				SolFinder.join(playclock); // wait at most clock seconds for solFinder to finish
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			while(SolFinder.isAlive()) { // if solFinder is still running interrupt it and wait for it to finish
		    	SolFinder.interrupt();
		    	try {
		    		SolFinder.join();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println("[ATAX] -> in getMove, Our Best Move, number="+SolFinder.MoveNumber+"; Value="+SolFinder.MaxValue);
			MoveNumber = SolFinder.MoveNumber;
			if (SolFinder.MaxValue == 0) {
				MoveNumber = random.nextInt( moves.size() );
				System.out.println("[ATAX] -> Playing Random Move because MaxValue=0");
			}
		}else{
			MoveNumber = random.nextInt( moves.size() );
			System.out.println("[ATAX] -> We have only one move, playing Random Move");
		}
		return moves.get(MoveNumber)[playerNumber];
    }

	public class MyStru {
		int GoalValue;
		int Depth;
		int MoveNumber;
		boolean Terminal;
		public MyStru(int GoalValue, int Depth, boolean Terminal,int MoveNumber){
			this.GoalValue=GoalValue;
			this.Depth=Depth;
			this.Terminal=Terminal;
			this.MoveNumber=MoveNumber;
		}

		public int getGoalValue(){
			return GoalValue;
		}

		public int getDepth(){
			return Depth;
		}

		public boolean getTerminal(){
			return Terminal;
		}

		public int getMoveNumber(){
			return MoveNumber;
		}
		public void setGoalValue(int Value){
			this.GoalValue=Value;
		}

		public void setDepth(int Depth){
			this.Depth=Depth;
		}

		public void setTerminal(boolean Terminal){
			this.Terminal=Terminal;
		}

		public void setMoveNumber(int MoveNumber){
			this.MoveNumber=MoveNumber;
		}
		
		public String toString(){
			return "GoalValue="+this.getGoalValue()+"; Depth="+this.getDepth()+"; Terminal="+this.getTerminal()+"; MoveNumber="+this.getMoveNumber();
		}
		
	}

}

